home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / Book Chapters / 00 - Game Programming Primer / MGW1Codeƒ / MGWSound1.c < prev    next >
Text File  |  1995-06-24  |  26KB  |  594 lines

  1. //==============================================================================================\\
  2. //        -------------------------------------------------------------------------------            \\
  3. //        MGWSound1.c version 1.0.0    copyright © 1993…1995 Jamie McCornack, john calhoun            \\
  4. //        -------------------------------------------------------------------------------            \\
  5. //         Sound management utilities for Macintosh GameWriter 1.0.0, a training program…            \\
  6. //        …for beginning Mac game programmers. MGW1 includes MGWExterns1.h, MGWUtilities1.c,…        \\
  7. //        …MGWSound1.c, MGWGraphics1.c, MGWGraphicsBWLite1.c, HelloWorld.rsrc and an assortment…    \\
  8. //        …of demo programs; projects HelloWorld1.π etc. and source code files HelloWorld1.c etc.    \\
  9. //         A tutorial is available in Tricks of the Mac Game Programming Gurus, published…        \\
  10. //        …by Hayden Books, August 1995.                                                                        \\
  11. //                                                                                                \\
  12. //        This code is offered by the copyright holders for no fee and for whatever use…            \\
  13. //        …you care to make of it, but we do hope you remember where it came from.                \\
  14. //                                                                                                \\
  15. //        Please send bug reports to MacGameDev at America OnLine.    macgamedev@aol.com            \\
  16. //        Suggestions and observations are also appreciated.                                        \\
  17. //        Updates and upgrades will be available now and then from the above e-mail address.        \\
  18. //==============================================================================================\\
  19.  
  20. #include "MGWExterns1.h"
  21. #include <Sound.h>
  22.  
  23. // Here is the constant that the SoundUnit uses internally
  24. #define        kOurCallBack    911    // This arbitrary number will identify a call back as our own.
  25.  
  26. // Here is the "global" variable used by the sound routines.
  27. Boolean        gUserWantsSound;    // A user-set variable used for turning on/off all sounds.
  28.  
  29. // Here are the internal variables used by MGWSound1.c. If you use the proper calls…
  30. // …(see the routines in MGWExterns.h) you should never need to access these externally.
  31. SndChannelPtr    soundChannel;    // This is the sound channel all our routines use
  32. short            soundPriority;    // This is the priority of the sound currently playing
  33. Boolean            canUseSound;    // Is FALSE if we can't use sound on this Mac
  34.  
  35.  
  36. //------------------------------------------------------------ Prototypes
  37.  
  38. void    FlushSoundNow (void);
  39. void    FlushSoundSoon (void);
  40. pascal void    SoundCallBack(SndChannelPtr chan, SndCommand theCommand);
  41. OSErr    InstallCallBack(void);
  42.  
  43.  
  44. //==============================================================  Functions
  45.  
  46. //--------------------------------------------------------------  CloseDownSound
  47.  
  48. // Call this function to dispose of the sound channel when switching out or quitting.
  49. // Otherwise, sound may be disabled for the next program you run.
  50.  
  51. OSErr CloseDownSound(void)
  52. {
  53.     OSErr    theErr;
  54.     
  55.     theErr = noErr;                        // Assume no error
  56.     
  57.     if (soundChannel != nil) {
  58.         theErr = SndDisposeChannel(soundChannel, TRUE);
  59.         soundChannel = nil;                // Set the variable to nil now
  60.     }
  61.     
  62.     soundPriority = kNoSoundPlaying;    // soundPriority needs to reflect no sound playing
  63.     
  64.     return(theErr);
  65. }
  66.  
  67. //--------------------------------------------------------------  InitializeForSound
  68.  
  69. // This procedure initializes all variables used by the sound unit routines.
  70. // It also does a check using SysEnvirons() to determine if sound will run at all.
  71. // You should have your program call this function only once at start up!
  72.  
  73. void    InitializeForSound()
  74. {
  75.     SysEnvRec    thisWorld;
  76.     
  77.     gUserWantsSound = TRUE;
  78.  
  79.     soundChannel = nil;                    // Initialize our sound channel to nil (very important).
  80.     soundPriority = kNoSoundPlaying;    // Indicate no sound is playing.
  81.  
  82.     SysEnvirons(1, &thisWorld);            // Call on SysEnvirons() to determine if we can play
  83.                                         // sound on this particular Mac and System version.
  84.                                         
  85.                     // Anything before the Mac II may not work for these routines.
  86.                     // Anything before System 6.0.5 may not work for these routines.
  87.     canUseSound = ((thisWorld.machineType >= envMacII) && (thisWorld.systemVersion >= 0x0605));
  88. }
  89.  
  90. //--------------------------------------------------------------  LoadASound
  91.  
  92. // Call this function to load a specific sound (by passing the ID of the 'snd ' resource
  93. // in your program).  It will load it, move it high in memory, and lock it.  It will
  94. // return FALSE if there was an error.  Errors could be due to low memory or because
  95. // the ID you specified was not found.
  96.  
  97. Boolean    LoadASound(short soundID)
  98. {
  99.     Handle        theSoundHandle;
  100.     Boolean        noProblem;
  101.  
  102.     theSoundHandle = Get1Resource('snd ', soundID);
  103.  
  104.     if (theSoundHandle == nil) {            // If the memory manager didn't assign a handle to
  105.         noProblem = FALSE;                    // theSoundHandle, then there was an error.
  106.     } else {
  107.         noProblem = TRUE;
  108.         MoveHHi(theSoundHandle);
  109.         HLock(theSoundHandle);
  110.     }
  111.  
  112.     return(noProblem);                        // Report back to calling routine.
  113. }
  114.  
  115. //--------------------------------------------------------------  LoadARangeOfSounds
  116.  
  117. // This function loads a range of sounds (say from ID = 2000 to ID = 2014).  You must
  118. // be quite sure however that all those sounds exist.  If anyone of them cannot be
  119. // found (or if memory is too low to permit them all to load) the function will return
  120. // FALSE indicating a failure to load one or more of the specified sounds.  Note that
  121. // this function just makes subsequent calls to the above function.
  122.  
  123. Boolean    LoadARangeOfSounds(short firstID, short lastID)
  124. {
  125.     short    i;
  126.     Boolean    noProblem;
  127.  
  128.     noProblem = TRUE;                        // Start by assuming there won't be an error.
  129.     
  130.     for (i = firstID; i <= lastID; i++) {
  131.         if (! LoadASound(i)) {                // If LoadASound returned FALSE on any sound
  132.             noProblem = FALSE;                //   in the range, there was a problem.
  133.         }
  134.     }
  135.  
  136.     return(noProblem);                        // Report back to calling routine.
  137. }
  138.  
  139. //--------------------------------------------------------------  LoadAllSounds
  140.  
  141. // This function takes a different strategy for loading sounds.  It uses the Resource
  142. // Manager routines to indicate the number of 'snd ' resources in your program
  143. // and then loads every one of them in the order that they appear in the resource fork.
  144.  
  145. Boolean    LoadAllSounds()
  146. {
  147.     Handle        theSoundHandle;
  148.     short        numberOfSounds, i;
  149.     Boolean        noProblem;
  150.  
  151.     noProblem = TRUE;                        // Start by assuming there won't be an error.
  152.  
  153.     numberOfSounds = Count1Resources('snd ');    // Determine # of sounds.
  154.     
  155.     for (i = 1; i <= numberOfSounds; i++) {    // Loop through all sounds.
  156.         theSoundHandle = Get1IndResource('snd ', i);    // Get each sound.
  157.  
  158.         if (theSoundHandle != nil) {        // Did we get a valid sound?
  159.             MoveHHi(theSoundHandle);        // Move sound handle high in memory
  160.             HLock(theSoundHandle);            //   And lock it!
  161.         } else {
  162.             noProblem = FALSE;                // But if we didn't get a valid sound, then we failed.
  163.         }
  164.     }
  165.     
  166.     return(noProblem);                        // Report back to calling routine.
  167. }
  168.  
  169. //--------------------------------------------------------------  ReleaseASound
  170.  
  171. // Call this function to unlock a specific sound (by passing the ID of the 'snd ' resource
  172. // in your program). Unlocking allows the memory manager to move or purge if needed.
  173. // It will return FALSE if there was an error.  Errors could be due to low memory (since it
  174. // will load first if the resource is not in memory) or because the ID you specified was not found.
  175.  
  176. Boolean ReleaseASound(short soundID)
  177. {
  178.     Handle        theSoundHandle;
  179.     Boolean        noProblem;
  180.  
  181.     theSoundHandle = Get1Resource('snd ', soundID);    // Finds sound handle in memory if it's loaded,
  182.                                                     //  otherwise loads it, so HUnlock will have a valid
  183.                                                     //  block to unlock.
  184.     if (theSoundHandle == nil) {
  185.         noProblem = FALSE;
  186.     } else {
  187.         noProblem = TRUE;
  188.         HUnlock(theSoundHandle);                    // Unlocks block, so Memory Manager can move or
  189.     }                                                //   purge it as needed.
  190.  
  191.     return(noProblem);                                // Report back to calling routine.
  192. }
  193.  
  194. //--------------------------------------------------------------  ReleaseARangeOfSounds
  195.  
  196. // This function unlocks a range of sounds (say from ID = 2000 to ID = 2014).  You must
  197. // be quite sure that all those sounds exist, though they need not be loaded into memory.
  198. // If anyone of them cannot be found (or if memory is too low to permit one to load) the function
  199. // will return FALSE indicating a failure to unlock one or more of the specified sounds.
  200. // Note that this function just makes subsequent calls to the above function.
  201.  
  202. Boolean    ReleaseARangeOfSounds(short firstID, short lastID)
  203. {
  204.     short        i;
  205.     Boolean        noProblem;
  206.  
  207.     noProblem = TRUE;                                // Start by assuming there won't be an error.
  208.     
  209.     for (i = firstID; i <= lastID; i++) {
  210.         if (! ReleaseASound(i)) {                    // If ReleaseASound returned FALSE on any sound
  211.             noProblem = FALSE;                        //   in the range, there was a problem.
  212.         }
  213.     }
  214.  
  215.     return(noProblem);                                // Report back to calling routine.
  216. }
  217.  
  218. //--------------------------------------------------------------  ReleaseAllSounds
  219.  
  220. // This function takes a different strategy for unlocking sounds.  It uses a Resource
  221. // Manager routine to indicate the number of 'snd ' resources in your program
  222. // and then unlocks every one of them in the order that they appear in the resource fork.
  223.  
  224. Boolean    ReleaseAllSounds()
  225. {
  226.     Handle        theSoundHandle;
  227.     short        numberOfSounds, i;
  228.     Boolean        noProblem;
  229.  
  230.     noProblem = TRUE;                                // Start by assuming there won't be a problem.
  231.  
  232.     numberOfSounds = Count1Resources('snd ');        // Determine # of sounds.
  233.     
  234.     for (i = 1; i <= numberOfSounds; i++) {            // Loop through all sounds.
  235.         theSoundHandle = Get1IndResource('snd ', i);// Get each sound.
  236.  
  237.         if (theSoundHandle != nil) {                // Did we get a valid sound?
  238.             HUnlock(theSoundHandle);                // And unlock it!
  239.         } else {
  240.             noProblem = FALSE;                        // But if we didn't get a valid sound, then we failed.
  241.         }
  242.     }
  243.     
  244.     return(noProblem);                                // Report back to calling routine.
  245. }
  246.  
  247. //--------------------------------------------------------------  SoundCallBack
  248.  
  249. // This procedure is never called from within the program.  The Mac will call this
  250. // procedure when the Sound Manager gets to a call back command in the sound queue.
  251. // Because this procedure gets called at interrupt time.  You cannot call any routine
  252. // that moves memory or creates it!  No GetResource(), NewHandle(), etc.
  253. // Furthermore, this procedure must always be present in memory!  For this to be
  254. // guaranteed, you should make sure the routine is in the Main segment of your
  255. // program. Put the whole SoundUnit in the Main memory segment in order to be safe.
  256. // This procedure sets soundPriority to zero, so other routines
  257. // can verify that a sound is no longer playing.
  258.  
  259.                 // Toolbox callback routines *must* be declared pascal
  260. pascal void    SoundCallBack(SndChannelPtr chan, SndCommand theCommand)
  261. {
  262.     long    theA5;
  263.     
  264.     if (theCommand.param1 == kOurCallBack) {        // Make sure it's our callBack.
  265.         theA5 = SetA5(theCommand.param2);            // The A5 when InstallCallBack Routine was called.
  266.         soundPriority = kNoSoundPlaying;            // Let the program know the sound is done.
  267.         theA5 = SetA5(theA5);                        // Restore the original A5. See IM6, 22-79.
  268.     }
  269. }
  270.  
  271. //--------------------------------------------------------------  InstallCallBack
  272.  
  273. // This function also is only used internally by the SoundUnit.  It is called right after
  274. // a sound is played asynchronously.  It will install the callBack command into the
  275. // sound queue so that after a sound finishes playing, our call back routine is called
  276. // and we can be alerted to the fact that a sound has finished playing.  This function
  277. // will return any error encountered.
  278.  
  279. OSErr    InstallCallBack()
  280. {
  281.     SndCommand    theCommand;
  282.     
  283.     theCommand.cmd = callBackCmd;                    // Set up the call back command.
  284.     theCommand.param1 = kOurCallBack;                // Flag this call back command as our own.
  285.     theCommand.param2 = SetCurrentA5();
  286.                                                     // Load the command into our sound channel.
  287.     return(SndDoCommand(soundChannel, &theCommand, FALSE));
  288. }
  289.  
  290. //--------------------------------------------------------------  IsSoundOn
  291.  
  292. // This function can be called to determine if sound is both desired and possible.
  293.  
  294. Boolean    IsSoundOn()
  295. {
  296.     return (canUseSound && gUserWantsSound);
  297. }
  298.  
  299. //--------------------------------------------------------------  ASoundIsPlaying
  300.  
  301. // A simple utility function that returns TRUE is a sound is playing or FALSE if not.
  302. // This is how you can externally determine if a sound is playing.
  303.  
  304. Boolean    ASoundIsPlaying()
  305. {
  306.     return(soundPriority != kNoSoundPlaying);
  307. }
  308.  
  309. //--------------------------------------------------------------  SoundPriorityPlaying
  310.  
  311. // A simple utility function that returns an integer indicating the priority of the
  312. // current sound playing.  It will return zero (kNoSoundPlaying) if that is the case.
  313. // This is how you can externally check on the state of the variable 'soundPriority'.
  314.  
  315. short    SoundPriorityPlaying()
  316. {
  317.     return(soundPriority);
  318. }
  319.  
  320. //--------------------------------------------------------------  ThisMacCanPlaySounds
  321.  
  322. // This utility allows the rest of your program access to the 'canUseSound' variable
  323. // that the SoundUnit uses.  This function will return TRUE if it is possible to play
  324. // sound on this Mac or FALSE if it is not.  As an example of this routines use, you
  325. // may have a menu item called 'Sound On' that the user can check or uncheck in
  326. // order to turn sounds in the game on or off.  If however they are running on a Mac
  327. // where it is impossible to play sounds, you would probably rather gray-out the menu
  328. // item entirely.  A call to the below function will let you know if sound is even an option.
  329.  
  330. Boolean    ThisMacCanPlaySounds()
  331. {
  332.     return(canUseSound);
  333. }
  334.  
  335.  
  336. //--------------------------------------------------------------  FlushSoundNow
  337.  
  338. // This routine is called internally to stop any sound currently playing and empty the…
  339. // …command queu of all upcoming sounds and commands.
  340.  
  341. void    FlushSoundNow (void)
  342. {
  343.     SndCommand    thisCommand;
  344.     OSErr        thisErr;
  345.  
  346.     thisCommand.cmd = flushCmd;    // Flush any upcoming commands from the command queu.
  347.     thisCommand.param1 = 0;        // This command ignores these parameters.
  348.     thisCommand.param2 = 0L;
  349.     thisErr = SndDoImmediate(soundChannel, &thisCommand);
  350.     
  351.     thisCommand.cmd = quietCmd;    // Send quietCmd to stop any current sound.
  352.     thisCommand.param1 = 0;        // This command ignores these parameters.
  353.     thisCommand.param2 = 0L;
  354.     thisErr = SndDoImmediate(soundChannel, &thisCommand);
  355. }
  356.  
  357. //--------------------------------------------------------------  FlushSoundSoon
  358.  
  359. // This routine is called internally to empty the command queu of all upcoming sounds…
  360. // …and commands, but leave the current sound playing.
  361.  
  362. void    FlushSoundSoon (void)
  363. {
  364.     SndCommand    thisCommand;
  365.     OSErr        thisErr;
  366.  
  367.     thisCommand.cmd = flushCmd;    // Flush any upcoming commands from the command queu.
  368.     thisCommand.param1 = 0;        // This command ignores these parameters.
  369.     thisCommand.param2 = 0L;
  370.     thisErr = SndDoImmediate(soundChannel, &thisCommand);
  371. }
  372.  
  373. // =========================================================================================\\
  374. //                             The Play…Sound routines                                            \\
  375. // =========================================================================================\\
  376. //    Here is where the results come from. At last, some routines that make sounds come out.    \\
  377. // =========================================================================================\\
  378.  
  379. //--------------------------------------------------------------  PlayASound
  380.  
  381. // Here we are.  This is the work horse routine used by the game to play sounds
  382. // asynchronously.  After starting the sound playing, control is…
  383. // immediately returned to our program.  This allows the action to continue!  You need
  384. // simply specify the ID of the sound you want to play as well as the priority at which
  385. // you want to play the sound (1 - 100).  If a sound of higher priority is already playing,
  386. // then the sound will not be played.
  387.  
  388. OSErr    PlayASound(short soundID, short priority)
  389. {
  390.     OSErr        theErr;
  391.     Handle        theSoundHandle;
  392.  
  393.     if (! IsSoundOn()) return;                        // Exit if we are not to play sounds.
  394.     
  395.     if (priority < soundPriority) return;            // Exit if a higher priority sound is playing.
  396.  
  397.     theSoundHandle = Get1Resource('snd ', soundID);    // Get the sound (only from our resource fork).
  398.     if (theSoundHandle == nil) return;                // Exit if we couldn't get the sound.
  399.  
  400.     if (! soundChannel == nil)                // If soundChannel is open, flush out…
  401.         FlushSoundNow();                    // …any commands in the soundChannel queu,…
  402.                                             // …and quiet the sound currently playing.
  403.     else                                                
  404.     {                                                // Create our sound channel.
  405.         theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
  406.                 if (theErr != noErr) return;        // Exit if that failed.
  407.     }
  408.                                             // Play the sound asynchronously.
  409.     theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
  410.     if (theErr != noErr) return;            // And exit if that failed.
  411.     
  412.     soundPriority = priority;        // Establish globally the priority of the sound playing.
  413.     
  414.     InstallCallBack();        // Lastly, queue up a callBackCmd to notify us…
  415.                             // when the sound has finished playing.
  416.     return(theErr);
  417. }
  418.  
  419.  
  420. //--------------------------------------------------------------  PlaySynchSound
  421.  
  422. // If you call this routine, the sound will be played and your program will wait here
  423. // until the sound is finished (playing synchronously).  Like the above routine, a sound
  424. // priority must be passed in as well as the ID of the sound to be played synchronously.
  425. // To override any other sound playing, pass it kHighestPriority for the priority.
  426.  
  427. OSErr    PlaySynchSound(short soundID, short priority)
  428. {
  429.     OSErr        theErr;
  430.     Handle        theSoundHandle;
  431.     
  432.     if (! IsSoundOn()) return;                        // Exit if we are not to play sounds.
  433.     
  434.     if (priority < soundPriority) return;            // Exit if a higher priority sound is playing.
  435.  
  436.     theSoundHandle = Get1Resource('snd ', soundID);    // Get the sound (only from our resource fork).
  437.     if (theSoundHandle == nil) return;                // Exit if we couldn't get the sound.
  438.  
  439.     if (! soundChannel == nil)                // If soundChannel is open, flush out…
  440.         FlushSoundNow();                    // …any commands in the soundChannel queu,…
  441.                                             // …and quiet the sound currently playing.
  442.     else                                                
  443.     {                                                // Create our sound channel.
  444.         theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)SoundCallBack);
  445.                 if (theErr != noErr) return;        // Exit if that failed.
  446.     }
  447.                     // Play the sound synchronously by passing FALSE.
  448.     theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, FALSE);
  449.     if (theErr != noErr) return;            // Exit if that failed, but if it works,…
  450.                     // …wait right here while the sound plays.
  451.     soundPriority = 0;        // Establish globally that the sound is done playing.
  452.     return(theErr);
  453. }
  454.  
  455. // =========================================================================================\\
  456. //                         The Feeping Creature routines                                        \\
  457. // =========================================================================================\\
  458. //    "Creeping Featurism" is a threat to good programs. If you throw in everything but…        \\
  459. //    …the kitchen sink, pretty soon you have an abomination like MS Word 6.0.                \\
  460. //    Creeping featurism is particularly disgusting in games,  many of which are so…            \\
  461. //    …complex as to be unplayable.                                                            \\
  462. //    Use these routines with care. Sound should enhance, not overpower, your games.            \\
  463. // =========================================================================================\\
  464.  
  465. //--------------------------------------------------------------  PlayLoopSound
  466.  
  467. // A pretty useful sound routine. It darn near deserves to be above the Feeping Creatures line.
  468. // Same as PlayASound except when PlayLoopSound encounters itself (or a sound with the same…
  469. // …soundPriority number) already playing (or as the most recent entry in the sound channel queu).
  470. // In that case, it calls FlushSoundSoon instead of FlushSound Now, and inserts the new sound…
  471. // …into the queu. The sound currently playing continues to play, followed immediately by the…
  472. // …soundID# passed to PlayLoopSound.
  473. // Useful when making repeated calls for background sounds, to avoid "stuttering" due to the…
  474. // …sound interrupting itself at inappropriate times or due to pausing between loops.
  475. // If you're looping a sound that's six ticks long, and you pass the sound ID# to PlayLoopSound…
  476. // and call it every five ticks, the sound will repeat itself seamlessly forever.
  477.  
  478. OSErr    PlayLoopSound(short soundID, short priority)
  479. {
  480.     OSErr        theErr;
  481.     Handle        theSoundHandle;
  482.  
  483.     if (! IsSoundOn()) return;                        // Exit if we are not to play sounds.
  484.     
  485.     if (priority < soundPriority) return;            // Exit if a higher priority sound is playing.
  486.  
  487.     theSoundHandle = Get1Resource('snd ', soundID);    // Get the sound (only from our resource fork).
  488.     if (theSoundHandle == nil) return;                // Exit if we couldn't get the sound.
  489.  
  490.     if (! soundChannel == nil)                // If soundChannel is open, flush out any upcoming…
  491.         FlushSoundSoon();                    // … commands in the soundChannel queu.
  492.     else                                                
  493.     {                                                // Create our sound channel.
  494.         theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
  495.                 if (theErr != noErr) return;        // Exit if that failed.
  496.     }
  497.                                             // Play the sound asynchronously.
  498.     theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
  499.     if (theErr != noErr) return;            // And exit if that failed.
  500.     
  501.     soundPriority = priority;        // Establish globally the priority of the sound playing.
  502.     
  503.     InstallCallBack();        // Lastly, queue up a callBackCmd to notify us…
  504.                             // when the sound has finished playing.
  505.     return(theErr);
  506. }
  507.  
  508.  
  509. //--------------------------------------------------------------  PlayLoopSoundOften
  510.  
  511. // This stuffs soundChannel with a loop that repeats over and over and over and over again.
  512. // Use sparingly, or the player will get real real sick of it. Real real real real sick of it.
  513. // Note the sound channel holds a finite number of commands (128 I think), and this routine…
  514. // …will run synchronously (that is, play the sound over and over while everything else is…
  515. // …locked up) for any sounds the queu can't hold. I don't pass more than 100 howManyLoops,…
  516. // …but there's a commented-out check for that if you need it.
  517.  
  518. OSErr    PlayLoopSoundOften(short soundID, short priority, short howManyLoops)
  519. {
  520.     OSErr        theErr;
  521.     Handle        theSoundHandle;
  522.     int            count;
  523.  
  524.     if (! IsSoundOn()) return;                    // Exit if we are not to play sounds.
  525.     
  526.     if (priority < soundPriority) return;        // Exit if a higher priority sound is playing.
  527.  
  528. //    if (howManyLoops > 100)                        // If an excessively large number is passed,…
  529. //            howManyLoops = 100;                    // …reduce it so it won't overflow the queu.
  530.  
  531.     theSoundHandle = Get1Resource('snd ', soundID);    // Get the sound (only from our resource fork).
  532.     if (theSoundHandle == nil) return;                // Exit if we couldn't get the sound.
  533.  
  534.     if (! soundChannel == nil)                // If soundChannel is open, flush out everything…
  535.         FlushSoundNow();                    // …from the soundChannel command queu.
  536.     else                                                
  537.     {                                                // Create our sound channel.
  538.         theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
  539.                 if (theErr != noErr) return;        // Exit if that failed.
  540.     }
  541.     for (count = 1; count <= howManyLoops; count++)
  542.     {                                        // Stuff the sound queu with sounds to play,…
  543.         theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
  544.         if (theErr != noErr) return;            // …and exit if that failed.
  545.     }
  546.     soundPriority = priority;        // Establish globally the priority of the sound playing.
  547.     
  548.     InstallCallBack();                // Lastly, queue up a callBackCmd to notify us…
  549.                                     // …when the sound has finished playing.
  550.     return(theErr);
  551. }
  552.  
  553. //--------------------------------------------------------------  AddSoundToQ
  554.  
  555. // AddSoundToQ does not flush the command queue from soundChannel, nor silence soundChannel, nor…
  556. // …check the priority of the sound currently playing, nor establish its own priority.
  557. // What it does is add (a command to play the selected sound) to the end of the soundChannel…
  558. // …command queu, and set soundPriority to zero.
  559. // Any call to any other Play…Sound routine will interrupt AddSoundToQ, and…
  560. // …AddSoundToQ will add its sound to the queu created by any Play…Sound routine. This routine…
  561. // …resides outside the entire soundPriority system, and must be called with care.
  562. // So what's AddSoundToQ good for? Stringing a series of sounds together. Put individual words…
  563. // in resource files, and play them back as sentences. Record assorted barnyard animal sounds,…
  564. // …sort them by pitch, store them as 'snd ' resources, and play them back as music.
  565. // The standard sound queu holds 128 commands; that's enough for some quite complex dialog…
  566. //…from ground control, or for a flock of sheep to baa a full chorus of "In the Mood," but don't…
  567. // …go over 128, or it'll play synchronously (that is, everything but the sound will lock up)…
  568. // …until the queue works its way back down to 128 commands.
  569.  
  570. void AddSoundToQ (short soundID)
  571. {
  572.     OSErr        theErr;
  573.     Handle        theSoundHandle;
  574.     
  575.         if (! IsSoundOn()) return;                    // Exit if we are not to play sounds.
  576.     
  577.         theSoundHandle = Get1Resource('snd ', soundID);    // Get the sound (only from our resource fork).
  578.         if (theSoundHandle == nil) return;                // Exit if we couldn't get the sound.
  579.  
  580.         if ( soundChannel == nil)                // If there's no soundChannel, open it.
  581.             theErr = SndNewChannel(&soundChannel, 0, initMono, (SndCallBackUPP)&SoundCallBack);
  582.                 if (theErr != noErr) return;        // Exit if that failed.
  583.  
  584.                                             // Add this sound to the the command queue.
  585.         theErr = SndPlay(soundChannel, (SndListHandle)theSoundHandle, TRUE);
  586.             
  587.         soundPriority = 0;            // Set it here instead of using the sound callback…
  588.                                     // …routine--saves queue space.
  589. }
  590.  
  591. //------------------------------------------------------------------------------------------\\
  592. //                                    End MGWSound1.c                                            \\
  593. //------------------------------------------------------------------------------------------\\
  594.